Pad validator withdrawals with a reward accrual buffer - #846
Merged
Merged
Conversation
tsudmi
previously approved these changes
Sep 20, 2026
evgeny-stakewise
approved these changes
Sep 22, 2026
evgeny-stakewise
approved these changes
Sep 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The operator has been sending
withdrawValidatorstransactions for dust amounts (61 gwei, 62 gwei, 3954 gwei) roughly every 12 hours on the Serenita vault, each costing more in gas than it withdraws.Root cause: the vault exit queue is share-denominated, so queued positions keep accruing rewards at every oracle update. The operator requested exactly the
getExitQueueMissingAssetsshortfall, floored to 1 gwei and truncated down. The requested withdrawal only reaches the vault after the consensus layer delay (about 27h for a partial withdrawal, up to ~11 days for a full validator exit) plus the next harvest, so every reward update during that window produced a fresh shortfall of ~12h of rewards on the remaining queue, and the operator immediately chased it with another transaction.Changes:
get_queued_assetsnow returnsExitQueueAssets(missing, total).missingis the net shortfall as before.totalis the value of the queued exit shares, the part of the queue that keeps accruing rewards. NewVaultContract.get_queued_exit_assetsreadsgetExitQueueData,totalAssetsandtotalSharesin one multicall afterupdateStateand converts the shares withOsTokenConverter. The checker cannot provide this value since it nets out the vault's liquid assets. Legacy asset-denominated exits are fixed and excluded. The extra read is skipped when there is no shortfall.calculate_withdrawal_bufferpads the request withmax(total * WITHDRAWAL_BUFFER_BPS / 10000, MIN_WITHDRAWAL_BUFFER_GWEI). 10 bps (0.1%) of the queue covers about 18 days of rewards at 2% APR, enough for both the partial withdrawal and the full exit path without modelling consensus layer processing. The floor covers queues too small for the ratio. Excess lands as withdrawable assets and is re-staked by the normal funding path, so the buffer is capped at 1 ETH (MAX_WITHDRAWAL_BUFFER_GWEI): a 2600 ETH queue would otherwise ask for 2.6 ETH that only goes straight back to funding.MIN_WITHDRAWAL_BUFFER_GWEIis an env setting (default 10000 gwei), checked at startup to be between 0 and the 1 ETH cap.MISSING_ASSETS_THRESHOLD_GWEIbecomes an env setting (default 1 gwei, so any positive shortfall is served and a small exit is paid once rather than left in the queue). A startup check caps it at 0.01 ETH, mirroring the oracle'sMISSING_ASSETS_THRESHOLD: above that the operator would skip shortfalls the oracle already treats as exit-worthy and covers with a full validator exit.For a 120 ETH queue the buffer is 0.12 ETH; for a 164 gwei remainder it is the 10000 gwei floor; for a 2600 ETH queue it is the 1 ETH cap.